/******************************************************************
 * Copyright (c) 2024 Bocloud Technologies Co., Ltd.
 * installer is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain n copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 ******************************************************************/

package webhooks

import (
	"context"
	"fmt"
	"net"
	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
	"strconv"
	"strings"

	jsonpatch "github.com/evanphx/json-patch"
	"github.com/pkg/errors"
	confv1beta1 "gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	bkeinit "gopkg.openfuyao.cn/bkecommon/cluster/initialize"
	bkenode "gopkg.openfuyao.cn/bkecommon/cluster/node"
	bkevalidate "gopkg.openfuyao.cn/bkecommon/cluster/validation"
	"gopkg.openfuyao.cn/bkecommon/security"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	kerrors "k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apimachinery/pkg/util/json"
	"k8s.io/apimachinery/pkg/util/validation/field"
	"sigs.k8s.io/cluster-api/util/version"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/webhook"

	bkev1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/v1beta1"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/phaseframe/phaseutil"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/annotation"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/clusterutil"
	l "gopkg.openfuyao.cn/cluster-api-provider-bke/utils/log"
)

var (
	log = l.Named("BKECluster Webhook")

	notAllowedPaths = [][]string{
		{"metadata", "annotations", "bke.bocloud.com/cluster-from"},
		{"spec", "controlPlaneEndpoint", "*"},
		{"spec", "clusterConfig", "cluster", "networking", "*"},
	}
	nonStandNotAllowedPaths = [][]string{
		{"metadata", "annotations", "bke.bocloud.com/cluster-from"},
		{"spec", "controlPlaneEndpoint", "*"},
	}
)

type BKECluster struct {
	Client client.Client
}

func (webhook *BKECluster) SetupWebhookWithManager(mgr ctrl.Manager) error {
	return ctrl.NewWebhookManagedBy(mgr).
		For(&bkev1beta1.BKECluster{}).
		WithValidator(webhook).
		WithDefaulter(webhook).
		Complete()
}

//+kubebuilder:webhook:verbs=create;update,path=/mutate-bke-bocloud-com-v1beta1-bkecluster,mutating=true,failurePolicy=fail,sideEffects=None,groups=bke.bocloud.com,resources=bkeclusters,versions=v1beta1,name=mbkecluster.kb.io,admissionReviewVersions=v1
//+kubebuilder:webhook:verbs=create;update,path=/validate-bke-bocloud-com-v1beta1-bkecluster,mutating=false,failurePolicy=fail,sideEffects=None,groups=bke.bocloud.com,resources=bkeclusters,versions=v1beta1,name=vbkecluster.kb.io,admissionReviewVersions=v1

var _ webhook.CustomDefaulter = &BKECluster{}
var _ webhook.CustomValidator = &BKECluster{}

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (webhook *BKECluster) Default(_ context.Context, obj runtime.Object) error {
	in, ok := obj.(*bkev1beta1.BKECluster)
	if !ok {
		return apierrors.NewBadRequest(fmt.Sprintf("expected a ClusterClass but got a %T", obj))
	}
	if in.Spec.ClusterConfig == nil {
		in.Spec.ClusterConfig = &confv1beta1.BKEConfig{}
	}
	cfg := bkeinit.BkeConfig(*in.Spec.ClusterConfig)
	bkeinit.SetDefaultBKEConfig(&cfg)
	in.Spec.ClusterConfig = (*confv1beta1.BKEConfig)(&cfg)
	setBKEClusterDefaultConfig(context.Background(), webhook.Client, in)

	// 设置默认注释功能门
	annotation.SetBKEClusterDefaultAnnotation(in)
	//设置默认集群入口
	nodes := bkenode.Nodes(in.Spec.ClusterConfig.Nodes)
	if nodes.Master().Length() != 0 && in.Spec.ControlPlaneEndpoint.IsZero() {
		endpointNode := nodes.Master()[0]
		if in.Spec.ControlPlaneEndpoint.Host == "" {
			in.Spec.ControlPlaneEndpoint.Host = endpointNode.IP
		}
		if in.Spec.ControlPlaneEndpoint.Port == 0 {
			in.Spec.ControlPlaneEndpoint.Port = bkeinit.DefaultAPIBindPort
		}
	}
	if in.Spec.ControlPlaneEndpoint.Host != "" && in.Spec.ControlPlaneEndpoint.Port == 0 {
		in.Spec.ControlPlaneEndpoint.Port = int32(bkeinit.DefaultLoadBalancerBindPort)
	}
	// 判断是否为 ha 集群 port是36443 并且 endpoint不在master节点上
	precheckPhase, ok := annotation.HasAnnotation(in, annotation.AtPrecheckPhaseAnnotationKey)
	// 如果有这个注释，且值为true，且暂停标志为true，则不进行webhook校验
	if ok && precheckPhase == "true" && in.Spec.Pause {
		if in.Spec.ControlPlaneEndpoint.IsValid() && nodes.Master().Length() != 0 {
			portflag := in.Spec.ControlPlaneEndpoint.Port == bkeinit.DefaultLoadBalancerBindPort
			// 不是36443端口且host不在master节点上 那说明需要更改host
			if !portflag && nodes.Filter(bkenode.FilterOptions{"IP": in.Spec.ControlPlaneEndpoint.Host}).Length() == 0 {
				in.Spec.ControlPlaneEndpoint.Host = nodes.Master()[0].IP
				in.Spec.ControlPlaneEndpoint.Port = bkeinit.DefaultAPIBindPort
			}
		}
	}

	//对于需要纳管的集群，不要设置集群版本默认值
	if !clusterutil.IsBKECluster(in) && !clusterutil.ClusterInfoHasCollected(in) {
		in.Spec.ClusterConfig.Cluster.KubernetesVersion = ""
	}

	// encrypt password
	newNodes := []confv1beta1.Node{}
	for _, node := range nodes {
		tmpNode := *node.DeepCopy()
		if node.Password == "" {
			newNodes = append(newNodes, tmpNode)
			continue
		}
		_, err := security.AesDecrypt(node.Password)
		if err == nil {
			newNodes = append(newNodes, tmpNode)
			continue
		}
		p, err := security.AesEncrypt(node.Password)
		if err != nil {
			newNodes = append(newNodes, tmpNode)
			continue
		}
		tmpNode.Password = p
		newNodes = append(newNodes, tmpNode)
	}
	in.Spec.ClusterConfig.Nodes = newNodes
	return nil
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (webhook *BKECluster) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
	oldBKECluster, ok := oldObj.(*bkev1beta1.BKECluster)
	if !ok {
		return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a BKECluster but got a %T", oldObj))
	}
	newBKECluster, ok := newObj.(*bkev1beta1.BKECluster)
	if !ok {
		return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a BKECluster but got a %T", newObj))
	}

	preCheckPhase, ok := annotation.HasAnnotation(newBKECluster, annotation.AtPrecheckPhaseAnnotationKey)
	// 如果有这个注释，且值为true，且暂停标志为true，则不进行webhook校验
	if ok && preCheckPhase == "true" && newBKECluster.Spec.Pause {
		return nil, nil
	}

	// 集群在部署中时的一些校验
	if newBKECluster.Status.ClusterHealthState == bkev1beta1.Deploying {
		// 在部署addon时，来暂停bc 不允许
		if newBKECluster.Status.ClusterStatus == bkev1beta1.ClusterDeployingAddon && newBKECluster.Spec.Pause {
			return nil, &apierrors.StatusError{
				ErrStatus: metav1.Status{
					Status:  metav1.StatusFailure,
					Code:    123,
					Message: "The cluster is deploying addon and cannot set pause",
				},
			}
		}
	}

	if clusterutil.FullyControlled(newBKECluster) {
		return nil, webhook.validateStandBKECluster(newBKECluster, oldBKECluster)
	}
	return nil, webhook.validateNonStandBKECluster(newBKECluster, oldBKECluster)
}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (webhook *BKECluster) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) {
	bkeCluster, ok := obj.(*bkev1beta1.BKECluster)
	if !ok {
		return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a ClusterClass but got a %T", obj))
	}
	// check all nodes all vip config is unique
	if err := webhook.ValidateBKEClustersNodesUnique(bkeCluster); err != nil {
		return nil, err
	}

	// skip validate bkecluster which is not bkecluster
	if !clusterutil.IsBKECluster(bkeCluster) {
		if bkeCluster.Spec.DryRun {
			return nil, apierrors.NewForbidden(
				bkev1beta1.GroupVersion.WithResource("bkeclusters").GroupResource(),
				bkeCluster.Name,
				errors.Errorf("BKECluster %s is not 'bke' type cluster, not support dryRun", bkeCluster.Name),
			)
		}
		return nil, nil
	}

	if err := bkevalidate.ValidateBKEConfig(*bkeCluster.Spec.ClusterConfig); err != nil {
		return nil, apierrors.NewBadRequest(fmt.Sprintf("invalide Spec.ClusterConfig, %v", err))
	}
	if err := webhook.ValidateControlPlaneEndpoint(bkeCluster); err != nil {
		return nil, err
	}
	return nil, nil
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (webhook *BKECluster) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
	return nil, nil
}

func (webhook *BKECluster) ValidateBKEClustersNodesUnique(bkeCluster *bkev1beta1.BKECluster) error {
	// 不是bke集群直接返回
	if !clusterutil.IsBKECluster(bkeCluster) {
		return nil
	}

	// get all bkeCluster
	bkeClusterLi := &bkev1beta1.BKEClusterList{}
	if err := webhook.Client.List(context.Background(), bkeClusterLi, &client.ListOptions{}); err != nil {
		return err
	}

	inNodes := bkenode.Nodes(bkeCluster.Spec.ClusterConfig.Nodes)
	endPoint := bkeCluster.Spec.ControlPlaneEndpoint

	errs := []metav1.StatusCause{}

	for _, bc := range bkeClusterLi.Items {
		if bc.Name == bkeCluster.Name {
			continue
		}
		if !clusterutil.IsBKECluster(&bc) {
			continue
		}

		if bc.Spec.ControlPlaneEndpoint.Host == endPoint.Host {
			errs = append(errs, metav1.StatusCause{
				Message: fmt.Sprintf("cluster %q controlPlaneEndpoint host %q is equal with cluster %q controlPlaneEndpoint host", bc.Name, endPoint.Host, bkeCluster.Name),
				Field:   field.NewPath("spec", "controlPlaneEndpoint", "host").String(),
			})
		}
		bcNodes := bkenode.Nodes(bc.Spec.ClusterConfig.Nodes)
		for i, node := range inNodes {
			if bcNodes.Filter(bkenode.FilterOptions{"IP": node.IP}).Length() != 0 {
				errs = append(errs, metav1.StatusCause{
					Message: fmt.Sprintf("Node %s is configured in both BKECluster %s and BKECluster %s", node.IP, utils.ClientObjNS(bkeCluster), utils.ClientObjNS(&bc)),
					Field:   field.NewPath("spec", "clusterConfig", "nodes").Index(i).String(),
				})
			}
		}
	}

	if len(errs) > 0 {
		return apierrors.NewApplyConflict(errs, bkeCluster.Name)
	}

	return nil
}

// ValidateControlPlaneEndpoint validates the control plane endpoint
func (webhook *BKECluster) ValidateControlPlaneEndpoint(bkeCluster *bkev1beta1.BKECluster) error {
	ControlPlaneEndpointPath := field.NewPath("Spec", "ControlPlaneEndpoint")
	ControlPlaneEndpointHostPath := ControlPlaneEndpointPath.Child("Host")
	ControlPlaneEndpointPortPath := ControlPlaneEndpointPath.Child("Port")

	switch {
	case bkeCluster.Spec.ControlPlaneEndpoint.Host != "" && net.ParseIP(bkeCluster.Spec.ControlPlaneEndpoint.Host) == nil:
		return field.Invalid(ControlPlaneEndpointHostPath, bkeCluster.Spec.ControlPlaneEndpoint.Host, "Host must be a valid IP string")
	case bkeCluster.Spec.ControlPlaneEndpoint.IsZero():
		return field.Required(ControlPlaneEndpointPath, "Host and Port must be set")
	case bkeCluster.Spec.ControlPlaneEndpoint.Host != "" && bkeCluster.Spec.ControlPlaneEndpoint.Port == 0:
		return field.Required(ControlPlaneEndpointPortPath, "Port must be set")
	case bkeCluster.Spec.ControlPlaneEndpoint.Host == "" && bkeCluster.Spec.ControlPlaneEndpoint.Port != 0:
		return field.Required(ControlPlaneEndpointHostPath, "Host must be set")
	case bkeCluster.Spec.ControlPlaneEndpoint.Port < 0 || bkeCluster.Spec.ControlPlaneEndpoint.Port > 65535:
		return field.Invalid(ControlPlaneEndpointPortPath, bkeCluster.Spec.ControlPlaneEndpoint.Port, "port can only be an integer between 0 and 65535")
	case bkeCluster.Spec.ControlPlaneEndpoint.IsValid():
		return nil
	default:
		return nil
	}
}

// validateVersionUpdate validate the cluster version update
func (webhook *BKECluster) validateVersionUpdate(newBKECluster, oldBKECluster *bkev1beta1.BKECluster) error {
	if oldBKECluster.Spec.ClusterConfig.Cluster.KubernetesVersion == "" {
		return nil
	}
	versionPath := field.NewPath("spec", "clusterConfig", "cluster", "kubernetesVersion")

	fromVersion, err := version.ParseMajorMinorPatch(oldBKECluster.Spec.ClusterConfig.Cluster.KubernetesVersion)
	if err != nil {
		return field.InternalError(versionPath, errors.Errorf("parse kubernetes version %q failed, err: %v", oldBKECluster.Spec.ClusterConfig.Cluster.KubernetesVersion, err))
	}
	toVersion, err := version.ParseMajorMinorPatch(newBKECluster.Spec.ClusterConfig.Cluster.KubernetesVersion)
	if err != nil {
		return field.InternalError(versionPath, errors.Errorf("parse kubernetes version %q failed, err: %v", newBKECluster.Spec.ClusterConfig.Cluster.KubernetesVersion, err))
	}

	// return early if kubernetes version not change
	if version.Compare(toVersion, fromVersion) == 0 {
		return nil
	}

	// compare version is upgrade
	if version.Compare(toVersion, fromVersion) < 0 {
		return field.Invalid(versionPath, newBKECluster.Spec.ClusterConfig.Cluster.KubernetesVersion, fmt.Sprintf("new kubernetes version %q is lower than old kubernetes version %q, that is not notAllowed", newBKECluster.Spec.ClusterConfig.Cluster.KubernetesVersion, oldBKECluster.Spec.ClusterConfig.Cluster.KubernetesVersion))
	}

	// it's upgrade now

	// return early if BKECluster is 'other' type
	if clusterutil.IsOtherCluster(newBKECluster) || clusterutil.IsOtherCluster(oldBKECluster) {
		return field.InternalError(versionPath, errors.Errorf("BKECluster %s is 'other' type cluster, not support upgrade kubernetes version", newBKECluster.Name))
	}

	// compare toVersion is lower than ExpectMinK8sVersion
	if toVersion.LT(bkev1beta1.ExpectMinK8sVersion) {
		return field.Invalid(versionPath, newBKECluster.Spec.ClusterConfig.Cluster.KubernetesVersion, fmt.Sprintf("new kubernetes version %q is lower than expect min kubernetes version %q, that is not notAllowed", newBKECluster.Spec.ClusterConfig.Cluster.KubernetesVersion, bkev1beta1.ExpectMinK8sVersion))
	}
	// compare toVersion is higher than ExpectMaxK8sVersion
	if toVersion.GTE(bkev1beta1.ExpectMaxK8sVersion) {
		return field.Invalid(versionPath, newBKECluster.Spec.ClusterConfig.Cluster.KubernetesVersion, fmt.Sprintf("new kubernetes version %q is higher than expect max kubernetes version %q, that is not notAllowed", newBKECluster.Spec.ClusterConfig.Cluster.KubernetesVersion, bkev1beta1.ExpectMaxK8sVersion))
	}

	// not allow upgrade if newBKECluster not healthy
	if newBKECluster.Status.ClusterHealthState != bkev1beta1.Healthy {
		return apierrors.NewForbidden(
			bkev1beta1.GroupVersion.WithResource("bkeclusters").GroupResource(),
			newBKECluster.Name,
			errors.Errorf("BKECluster %s is not newBKECluster a ready state and cannot be upgraded", newBKECluster.Name),
		)
	}

	// not allow update any nodes any field
	nodesT, nodeFlag := bkenode.CompareBKEConfigNode(oldBKECluster.Spec.ClusterConfig.Nodes, newBKECluster.Spec.ClusterConfig.Nodes)
	if nodeFlag {
		for _, nodeT := range nodesT {
			log.Debugf("node %s change, %v", phaseutil.NodeInfo(*nodeT.Node), nodeT.Operate)
		}

		return apierrors.NewForbidden(
			bkev1beta1.GroupVersion.WithResource("bkeclusters").GroupResource(),
			newBKECluster.Name,
			errors.Errorf("The cluster version cannot be changed at the same time as the nodes filed change"),
		)
	}

	errs := []error{}
	// 排除所有具有失败状态的节点判断agent是否alive
	for _, nodeState := range newBKECluster.Status.NodesStatus {
		nodeIP := nodeState.Node.IP
		if newBKECluster.GetNodeStateFlag(nodeIP, bkev1beta1.NodeFailedFlag) {
			continue
		}
		if !newBKECluster.GetNodeStateFlag(nodeIP, bkev1beta1.NodeAgentReadyFlag) {
			errs = append(errs, fmt.Errorf("node %s BKEAent is not alive, cannot be upgraded", nodeIP))
		}
	}
	if len(errs) > 0 {
		return apierrors.NewForbidden(
			bkev1beta1.GroupVersion.WithResource("bkeclusters").GroupResource(),
			newBKECluster.Name,
			kerrors.NewAggregate(errs),
		)
	}

	return nil
}

// validateDryRun check status is not allowed to update
func (webhook *BKECluster) validateDryRun(newBKECluster, oldBKECluster *bkev1beta1.BKECluster) error {
	dryRunFlag := newBKECluster.Spec.DryRun || oldBKECluster.Spec.DryRun
	if dryRunFlag && !clusterutil.IsBKECluster(newBKECluster) {
		return apierrors.NewForbidden(
			bkev1beta1.GroupVersion.WithResource("bkeclusters").GroupResource(),
			newBKECluster.Name,
			errors.Errorf("BKECluster %s is %q type cluster, not support dryRun", newBKECluster.Name, clusterutil.GetClusterType(newBKECluster)),
		)
	}
	return nil
}

func (webhook *BKECluster) validateStandBKECluster(newBKECluster, oldBKECluster *bkev1beta1.BKECluster) error {
	if err := webhook.validateFieldUpdate(newBKECluster, oldBKECluster, notAllowedPaths); err != nil {
		return err
	}

	if err := webhook.validateVersionUpdate(newBKECluster, oldBKECluster); err != nil {
		return err
	}

	if err := webhook.validateDryRun(newBKECluster, oldBKECluster); err != nil {
		return err
	}

	if !clusterutil.IsBKECluster(newBKECluster) {
		if err := bkevalidate.ValidateNonStandardBKEConfig(*newBKECluster.Spec.ClusterConfig); err != nil {
			return apierrors.NewBadRequest(fmt.Sprintf("invalide Spec.ClusterConfig, %v", err))
		}
		return nil
	}

	if err := bkevalidate.ValidateBKEConfig(*newBKECluster.Spec.ClusterConfig); err != nil {
		return apierrors.NewBadRequest(fmt.Sprintf("invalide Spec.ClusterConfig, %v", err))
	}
	return nil
}

func (webhook *BKECluster) validateNonStandBKECluster(newBKECluster, oldBKECluster *bkev1beta1.BKECluster) error {
	if err := webhook.validateFieldUpdate(newBKECluster, oldBKECluster, nonStandNotAllowedPaths); err != nil {
		return err
	}
	if err := bkevalidate.ValidateNonStandardBKEConfig(*newBKECluster.Spec.ClusterConfig); err != nil {
		return apierrors.NewBadRequest(fmt.Sprintf("invalide Spec.ClusterConfig, %v", err))
	}
	return nil
}

func (webhook *BKECluster) validateFieldUpdate(newBKECluster, oldBKECluster *bkev1beta1.BKECluster, notAllowedPaths [][]string) error {
	originalJSON, err := json.Marshal(oldBKECluster)
	if err != nil {
		return apierrors.NewInternalError(err)
	}
	modifiedJSON, err := json.Marshal(newBKECluster)
	if err != nil {
		return apierrors.NewInternalError(err)
	}

	diff, err := jsonpatch.CreateMergePatch(originalJSON, modifiedJSON)
	if err != nil {
		return apierrors.NewInternalError(err)
	}
	jsonPatch := map[string]interface{}{}
	if err := json.Unmarshal(diff, &jsonPatch); err != nil {
		return apierrors.NewInternalError(err)
	}
	// Build a list of all paths that are trying to change
	diffpaths := paths([]string{}, jsonPatch)

	allErrs := field.ErrorList{}

	for _, path := range diffpaths {
		// Ignore paths that are empty
		if len(path) == 0 {
			continue
		}
		if notAllowed(notAllowedPaths, path) {
			if len(path) == 1 {
				allErrs = append(allErrs, field.Forbidden(field.NewPath(path[0]), "cannot be modified"))
				continue
			}
			allErrs = append(allErrs, field.Forbidden(field.NewPath(path[0], path[1:]...), "cannot be modified"))
		}
	}
	if len(allErrs) > 0 {
		return apierrors.NewInvalid(bkev1beta1.GroupVersion.WithKind("BKECluster").GroupKind(), "BKECluster", allErrs)
	}
	return nil
}

func notAllowed(allowList [][]string, path []string) bool {
	for _, allowed := range allowList {
		if pathsMatch(allowed, path) {
			return true
		}
	}
	return false
}

// paths builds a slice of paths that are being modified.
func paths(path []string, diff map[string]interface{}) [][]string {
	allPaths := [][]string{}
	for key, m := range diff {
		nested, ok := m.(map[string]interface{})
		if !ok {
			// We have to use a copy of path, because otherwise the slice we append to
			// allPaths would be overwritten in another iteration.
			tmp := make([]string, len(path))
			copy(tmp, path)
			allPaths = append(allPaths, append(tmp, key))
			continue
		}
		allPaths = append(allPaths, paths(append(path, key), nested)...)
	}
	return allPaths
}

func pathsMatch(allowed, path []string) bool {
	// if either are empty then no match can be made
	if len(allowed) == 0 || len(path) == 0 {
		return false
	}
	i := 0
	for i = range path {
		// reached the end of the notAllowed path and no match was found
		if i > len(allowed)-1 {
			return false
		}
		if allowed[i] == "*" {
			return true
		}
		if path[i] != allowed[i] {
			return false
		}
	}
	// path has been completely iterated and has not matched the end of the path.
	// e.g. notAllowed: []string{"a","b","c"}, path: []string{"a"}
	return i >= len(allowed)-1
}

func setBKEClusterDefaultConfig(ctx context.Context, c client.Client, bkeCluster *bkev1beta1.BKECluster) {
	if bkeCluster == nil {
		return
	}

	log := log.With("bkeCluster", utils.ClientObjNS(bkeCluster))

	data, err := clusterutil.GetBKEConfigCMData(ctx, c)
	if err != nil {
		log.Errorf("get bke-config configmap data failed, err:%v", err)
		return
	}
	if data == nil {
		log.Errorf("bke-config configmap data is nil")
		return
	}

	patchFuncs := []func(cluster *bkev1beta1.BKECluster){}

	// set repo port
	yumPort := data["yumRepoPort"]
	if yumPort != "" && bkeCluster.Spec.ClusterConfig.Cluster.HTTPRepo.Port == "" {
		patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
			cluster.Spec.ClusterConfig.Cluster.HTTPRepo.Port = yumPort
		})
	}

	imagePort := data["imageRepoPort"]
	if imagePort != "" && bkeCluster.Spec.ClusterConfig.Cluster.ImageRepo.Port == "" {
		patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
			cluster.Spec.ClusterConfig.Cluster.ImageRepo.Port = imagePort
		})
	}
	// set image repo prefix
	imageRepoPrefix := data["imageRepoPrefix"]
	if imageRepoPrefix != "" && bkeCluster.Spec.ClusterConfig.Cluster.ImageRepo.Prefix == "" {
		patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
			cluster.Spec.ClusterConfig.Cluster.ImageRepo.Prefix = imageRepoPrefix
		})
	}

	// set ntp server
	// 纳管集群不强制设置ntp默认值
	if !clusterutil.IsBocloudCluster(bkeCluster) && !clusterutil.IsOtherCluster(bkeCluster) {
		ntpServer := data["ntpServer"]
		if ntpServer != "" && bkeCluster.Spec.ClusterConfig.Cluster.NTPServer == "" {
			patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
				cluster.Spec.ClusterConfig.Cluster.NTPServer = ntpServer
			})
		}
	}

	// set custom extra
	if bkeCluster.Spec.ClusterConfig.CustomExtra == nil {
		bkeCluster.Spec.ClusterConfig.CustomExtra = map[string]string{}
	}

	// set containerd
	containerd := data["containerd"]
	if containerd != "" {
		containerd = strings.ReplaceAll(containerd, "amd64", "{.arch}")
		containerd = strings.ReplaceAll(containerd, "arm64", "{.arch}")
		if _, ok := bkeCluster.Spec.ClusterConfig.CustomExtra["containerd"]; !ok {
			patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
				cluster.Spec.ClusterConfig.CustomExtra["containerd"] = containerd
			})
		}
	}

	host := data["host"]
	if host != "" {
		// set repo ip
		if bkeCluster.Spec.ClusterConfig.Cluster.HTTPRepo.Ip == "" {
			patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
				cluster.Spec.ClusterConfig.Cluster.HTTPRepo.Ip = host
			})
		}

		// set repo ip
		if bkeCluster.Spec.ClusterConfig.Cluster.ImageRepo.Ip == "" {
			patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
				cluster.Spec.ClusterConfig.Cluster.ImageRepo.Ip = host
			})
		}

		// set init machine host
		if _, ok := bkeCluster.Spec.ClusterConfig.CustomExtra["host"]; !ok {
			patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
				cluster.Spec.ClusterConfig.CustomExtra["host"] = host
			})

		}
		// set nfs server
		if _, ok := bkeCluster.Spec.ClusterConfig.CustomExtra["nfsServer"]; !ok {
			patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
				cluster.Spec.ClusterConfig.CustomExtra["nfsServer"] = host
			})
		}

		// set nfs root dir
		if _, ok := bkeCluster.Spec.ClusterConfig.CustomExtra["nfsRootDir"]; !ok {
			patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
				cluster.Spec.ClusterConfig.CustomExtra["nfsRootDir"] = "/"
			})
		}

		// set nfs version
		if _, ok := bkeCluster.Spec.ClusterConfig.CustomExtra["nfsVersion"]; !ok {
			patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
				cluster.Spec.ClusterConfig.CustomExtra["nfsVersion"] = "4.1"
			})
		}

		// set deployServerIp for bocoperator
		for i, addon := range bkeCluster.Spec.ClusterConfig.Addons {
			if addon.Name == "bocoperator" {
				patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
					cluster.Spec.ClusterConfig.Addons[i].Param["deployServerIp"] = host
				})
			}
		}
	}

	// set pipelineServer for bocoperator
	for _, addon := range bkeCluster.Spec.ClusterConfig.Addons {
		addonCopy := addon.DeepCopy()
		if addonCopy.Name == "bocoperator" {
			bkeNodes := bkenode.Nodes(bkeCluster.Spec.ClusterConfig.Nodes)
			masterNodes := bkeNodes.Master().Decrypt()
			// 添加pipelineServer到customExtra，以便agent在安装docker时根据ip来判断是否开启TLS
			if _, ok := bkeCluster.Spec.ClusterConfig.CustomExtra["pipelineServer"]; !ok {
				patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
					cluster.Spec.ClusterConfig.CustomExtra["pipelineServer"] = masterNodes[0].IP
				})
			}
			if _, ok := bkeCluster.Spec.ClusterConfig.CustomExtra["pipelineServerEnableCleanImages"]; !ok {
				patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
					cluster.Spec.ClusterConfig.CustomExtra["pipelineServerEnableCleanImages"] = "false"
				})

			}
		}
		if addonCopy.Name == "kubeproxy" {
			if addonCopy.Param["proxyMode"] == "" {
				patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
					cluster.Spec.ClusterConfig.CustomExtra["proxyMode"] = "iptables"
				})
			} else {
				patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
					cluster.Spec.ClusterConfig.CustomExtra["proxyMode"] = addonCopy.Param["proxyMode"]
				})
			}
		}
	}

	// set masterVirtualRouterId
	masterVirtualRouterIdSet := false
	masterVirtualRouterId := 0
	if clusterutil.AvailableLoadBalancerEndPoint(bkeCluster.Spec.ControlPlaneEndpoint, bkeCluster.Spec.ClusterConfig.Nodes) {
		if _, ok := bkeCluster.Spec.ClusterConfig.CustomExtra["masterVirtualRouterId"]; !ok {
			masterVirtualRouterId = utils.Random(1, 255)
			masterVirtualRouterIdSet = true
			patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
				cluster.Spec.ClusterConfig.CustomExtra["masterVirtualRouterId"] = strconv.Itoa(masterVirtualRouterId)
			})
		}
	}

	// set ingressVirtualRouterId
	ingressVirtualRouterIdSet := false
	ingressVirtualRouterId := 0
	if vip, _ := clusterutil.GetIngressConfig(bkeCluster.Spec.ClusterConfig.Addons); vip != "" {
		if _, ok := bkeCluster.Spec.ClusterConfig.CustomExtra["ingressVirtualRouterId"]; !ok {
			ingressVirtualRouterId = utils.Random(1, 255)
			ingressVirtualRouterIdSet = true
			patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
				cluster.Spec.ClusterConfig.CustomExtra["ingressVirtualRouterId"] = strconv.Itoa(ingressVirtualRouterId)
			})
		}
	}

	// check masterVirtualRouterId != ingressVirtualRouterId
	if masterVirtualRouterIdSet && ingressVirtualRouterIdSet && masterVirtualRouterId == ingressVirtualRouterId {
		for masterVirtualRouterId != ingressVirtualRouterId {
			ingressVirtualRouterId = utils.Random(1, 255)
		}
		patchFuncs = append(patchFuncs, func(cluster *bkev1beta1.BKECluster) {
			cluster.Spec.ClusterConfig.CustomExtra["ingressVirtualRouterId"] = strconv.Itoa(ingressVirtualRouterId)
		})
	}

	for _, patchFunc := range patchFuncs {
		patchFunc(bkeCluster)
	}
	return
}
